/*
 Copyright (c) 2024 HigginsSoft, Alexander Higgins - https://github.com/alexhiggins732/ 

 Copyright (c) 2018, Brock Allen & Dominick Baier. All rights reserved.

 Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 
 Source code and license this software can be found 

 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.
*/

namespace IdentityServer8.Test;

/// <summary>
/// Store for test users
/// </summary>
public class TestUserStore
{
    private readonly List<TestUser> _users;

    /// <summary>
    /// Initializes a new instance of the <see cref="TestUserStore"/> class.
    /// </summary>
    /// <param name="users">The users.</param>
    public TestUserStore(List<TestUser> users)
    {
        _users = users;
    }

    /// <summary>
    /// Validates the credentials.
    /// </summary>
    /// <param name="username">The username.</param>
    /// <param name="password">The password.</param>
    /// <returns></returns>
    public bool ValidateCredentials(string username, string password)
    {
        var user = FindByUsername(username);
        
        if (user != null)
        {
            if (string.IsNullOrWhiteSpace(user.Password) && string.IsNullOrWhiteSpace(password))
            {
                return true;
            }
            
            return user.Password.Equals(password);
        }
        
        return false;
    }

    /// <summary>
    /// Finds the user by subject identifier.
    /// </summary>
    /// <param name="subjectId">The subject identifier.</param>
    /// <returns></returns>
    public TestUser FindBySubjectId(string subjectId)
    {
        return _users.FirstOrDefault(x => x.SubjectId == subjectId);
    }

    /// <summary>
    /// Finds the user by username.
    /// </summary>
    /// <param name="username">The username.</param>
    /// <returns></returns>
    public TestUser FindByUsername(string username)
    {
        return _users.FirstOrDefault(x => x.Username.Equals(username, StringComparison.OrdinalIgnoreCase));
    }

    /// <summary>
    /// Finds the user by external provider.
    /// </summary>
    /// <param name="provider">The provider.</param>
    /// <param name="userId">The user identifier.</param>
    /// <returns></returns>
    public TestUser FindByExternalProvider(string provider, string userId)
    {
        return _users.FirstOrDefault(x =>
            x.ProviderName == provider &&
            x.ProviderSubjectId == userId);
    }

    /// <summary>
    /// Automatically provisions a user.
    /// </summary>
    /// <param name="provider">The provider.</param>
    /// <param name="userId">The user identifier.</param>
    /// <param name="claims">The claims.</param>
    /// <returns></returns>
    public TestUser AutoProvisionUser(string provider, string userId, List<Claim> claims)
    {
        // create a list of claims that we want to transfer into our store
        var filtered = new List<Claim>();

        foreach (var claim in claims)
        {
            // if the external system sends a display name - translate that to the standard OIDC name claim
            if (claim.Type == ClaimTypes.Name)
            {
                filtered.Add(new Claim(JwtClaimTypes.Name, claim.Value));
            }
            // if the JWT handler has an outbound mapping to an OIDC claim use that
            else if (JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.ContainsKey(claim.Type))
            {
                filtered.Add(new Claim(JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap[claim.Type], claim.Value));
            }
            // copy the claim as-is
            else
            {
                filtered.Add(claim);
            }
        }

        // if no display name was provided, try to construct by first and/or last name
        if (!filtered.Any(x => x.Type == JwtClaimTypes.Name))
        {
            var first = filtered.FirstOrDefault(x => x.Type == JwtClaimTypes.GivenName)?.Value;
            var last = filtered.FirstOrDefault(x => x.Type == JwtClaimTypes.FamilyName)?.Value;
            if (first != null && last != null)
            {
                filtered.Add(new Claim(JwtClaimTypes.Name, first + " " + last));
            }
            else if (first != null)
            {
                filtered.Add(new Claim(JwtClaimTypes.Name, first));
            }
            else if (last != null)
            {
                filtered.Add(new Claim(JwtClaimTypes.Name, last));
            }
        }

        // create a new unique subject id
        var sub = CryptoRandom.CreateUniqueId(format: CryptoRandom.OutputFormat.Hex);

        // check if a display name is available, otherwise fallback to subject id
        var name = filtered.FirstOrDefault(c => c.Type == JwtClaimTypes.Name)?.Value ?? sub;

        // create new user
        var user = new TestUser
        {
            SubjectId = sub,
            Username = name,
            ProviderName = provider,
            ProviderSubjectId = userId,
            Claims = filtered
        };

        // add user to in-memory store
        _users.Add(user);

        return user;
    }
}
